﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Text;
using System.Data;

namespace ColorApplication.Helper
{
  
       /// <summary>
       /// Class to read csv content from various sources
       /// </summary>
       public sealed class CsvReader : IDisposable
       {
   
           #region Members
   
           private FileStream _fileStream;
           private Stream _stream;
           private StreamReader _streamReader;
           private StreamWriter _streamWriter;
           private Stream _memoryStream;
           private Encoding _encoding;
           private readonly StringBuilder _columnBuilder = new StringBuilder(100);
           private readonly Type _type = Type.File;

           private string[] columnarry = { "Sub-color name", "R value", "G value", "B value", "Pixels amount", "Pixels pecentage", "Column 7", "Column 8", "Column 9", "Column 10", "Column 11" };

           #endregion Members
   
           #region Properties
   
           /// <summary>
           /// Gets or sets whether column values should be trimmed
           /// </summary>
           public bool TrimColumns { get; set; }
   
           /// <summary>
           /// Gets or sets whether the csv file has a header row
           /// </summary>
           public bool HasHeaderRow { get; set; }
   
           /// <summary>
           /// Returns a collection of fields or null if no record has been read
           /// </summary>
           public List<string> Fields { get; private set; }
   
           /// <summary>
           /// Gets the field count or returns null if no fields have been read
           /// </summary>
           public int? FieldCount
           {
               get
               {
                   return (Fields != null ? Fields.Count : (int?)null);
               }
           }
   
           #endregion Properties
   
           #region Enums
   
           /// <summary>
           /// Type enum
           /// </summary>
           private enum Type
           {
               File,
               Stream,
               String
           }
   
           #endregion Enums
   
           #region Constructors
   
           /// <summary>
           /// Initialises the reader to work from a file
          /// </summary>
          /// <param name="filePath">File path</param>
          public CsvReader(string filePath)
          {
              _type = Type.File;
              Initialise(filePath, Encoding.Default);
          }
  
          /// <summary>
          /// Initialises the reader to work from a file
          /// </summary>
          /// <param name="filePath">File path</param>
          /// <param name="encoding">Encoding</param>
          public CsvReader(string filePath, Encoding encoding)
          {
              _type = Type.File;
              Initialise(filePath, encoding);
          }
  
          /// <summary>
          /// Initialises the reader to work from an existing stream
          /// </summary>
          /// <param name="stream">Stream</param>
          public CsvReader(Stream stream)
          {
              _type = Type.Stream;
              Initialise(stream, Encoding.Default);
          }
  
          /// <summary>
          /// Initialises the reader to work from an existing stream
          /// </summary>
          /// <param name="stream">Stream</param>
          /// <param name="encoding">Encoding</param>
          public CsvReader(Stream stream, Encoding encoding)
          {
              _type = Type.Stream;
              Initialise(stream, encoding);
          }
  
          /// <summary>
          /// Initialises the reader to work from a csv string
          /// </summary>
          /// <param name="encoding"></param>
          /// <param name="csvContent"></param>
          public CsvReader(Encoding encoding, string csvContent)
          {
              _type = Type.String;
              Initialise(encoding, csvContent);  
          }
  
          #endregion Constructors
  
           #region Methods
  
          /// <summary>
          /// Initialises the class to use a file
          /// </summary>
          /// <param name="filePath"></param>
          /// <param name="encoding"></param>
          private void Initialise(string filePath, Encoding encoding)
          {
              if (!File.Exists(filePath))
                  throw new FileNotFoundException(string.Format("The file '{0}' does not exist.", filePath));
  
              _fileStream = File.OpenRead(filePath);
              Initialise(_fileStream, encoding);
          }
  
          /// <summary>
          /// Initialises the class to use a stream
          /// </summary>
          /// <param name="stream"></param>
          /// <param name="encoding"></param>
          private void Initialise(Stream stream, Encoding encoding)
          {
              if (stream == null)
                  throw new ArgumentNullException("The supplied stream is null.");
  
              _stream = stream;
              _stream.Position = 0;
              _encoding = (encoding ?? Encoding.Default);
              _streamReader = new StreamReader(_stream, _encoding);
          }
  
          /// <summary>
          /// Initialies the class to use a string
          /// </summary>
          /// <param name="encoding"></param>
          /// <param name="csvContent"></param>
          private void Initialise(Encoding encoding, string csvContent)
          {
              if (csvContent == null)
                  throw new ArgumentNullException("The supplied csvContent is null.");
  
              _encoding = (encoding ?? Encoding.Default);
  
              _memoryStream = new MemoryStream(csvContent.Length);
              _streamWriter = new StreamWriter(_memoryStream);
              _streamWriter.Write(csvContent);
              _streamWriter.Flush();
              Initialise(_memoryStream, encoding);           
          }
  
          /// <summary>
          /// Reads the next record
          /// </summary>
          /// <returns>True if a record was successfuly read, otherwise false</returns>
          public bool ReadNextRecord()
          {
              Fields = null;
              string line = _streamReader.ReadLine();
  
              if (line == null)
                  return false;
  
              ParseLine(line);
              return true;
          }
  
          /// <summary>
          /// Reads a csv file format into a data table.  This method
          /// Set Custome Colums Name
          /// to determine the columns.
          /// </summary>
          /// <returns></returns>
          public DataTable ReadIntoDataTable()
          {
              return ReadIntoDataTable(new System.Type[] {});
          }
  
          /// <summary>
          /// Reads a csv file format into a data table.  This method
          /// will always assume that the table has a header row as this will be used
          /// to determine the columns.
          /// </summary>
          /// <param name="columnTypes">Array of column types</param>
          /// <returns></returns>
          public DataTable ReadIntoDataTable(System.Type[] columnTypes)
          {
              DataTable dataTable = new DataTable();
              bool addedHeader = false;
              _stream.Position = 0;
  
              while (ReadNextRecord())
              {
                  if (!addedHeader)
                  {

                      //for (int i = 0; i < Fields.Count; i++)
                      //{
                      //    string ColumnName = "Column" + (i+1);
                      //    dataTable.Columns.Add(ColumnName, (columnTypes.Length > 0 ? columnTypes[i] : typeof(string)));
                      //    //dataTable.Columns.Add(Fields[i], (columnTypes.Length > 0 ? columnTypes[i] : typeof(string)));
               
                         
                      //}

                      for (int i = 0; i < Fields.Count; i++)
                      {
                          string ColumnName = columnarry[i];
                          dataTable.Columns.Add(ColumnName, (columnTypes.Length > 0 ? columnTypes[i] : typeof(string)));
                          //dataTable.Columns.Add(Fields[i], (columnTypes.Length > 0 ? columnTypes[i] : typeof(string)));


                      }
                      DataRow row1 = dataTable.NewRow();

                      for (int j = 0; j < Fields.Count; j++)
                          row1[j] = Fields[j];

                      dataTable.Rows.Add(row1);
                      addedHeader = true;
                      continue;
                  }
  
                  DataRow row = dataTable.NewRow();
  
                  for (int i = 0; i < Fields.Count; i++)
                      row[i] = Fields[i];
  
                  dataTable.Rows.Add(row);
              }
  
              return dataTable;
          }
  
          /// <summary>
          /// Parses a csv line
          /// </summary>
          /// <param name="line">Line</param>
          private void ParseLine(string line)
          {
              Fields = new List<string>();
              bool inColumn = false;
              bool inQuotes = false;
              _columnBuilder.Remove(0, _columnBuilder.Length);
  
              // Iterate through every character in the line
              for (int i = 0; i < line.Length; i++)
              {
                  char character = line[i];
  
                  // If we are not currently inside a column
                  if (!inColumn)
                  {
                      // If the current character is a double quote then the column value is contained within
                      // double quotes, otherwise append the next character
                      if (character == '"')
                          inQuotes = true;
                      else
                          _columnBuilder.Append(character);
  
                      inColumn = true;
                      continue;
                  }
  
                  // If we are in between double quotes
                  if (inQuotes)
                  {
                      // If the current character is a double quote and the next character is a comma or we are at the end of the line
                      // we are now no longer within the column.
                      // Otherwise increment the loop counter as we are looking at an escaped double quote e.g. "" within a column
                      if (character == '"' && ((line.Length > (i + 1) && line[i + 1] == ',') || ((i + 1) == line.Length)))
                      {
                          inQuotes = false;
                          inColumn = false;
                          i++;
                      }
                      else if (character == '"' && line.Length > (i + 1) && line[i + 1] == '"')
                          i++;
                  }
                  else if (character == ',')
                      inColumn = false;
  
                  // If we are no longer in the column clear the builder and add the columns to the list
                  if (!inColumn)
                  {
                      Fields.Add(TrimColumns ? _columnBuilder.ToString().Trim() : _columnBuilder.ToString());
                      _columnBuilder.Remove(0, _columnBuilder.Length);
                  }
                  else // append the current column
                      _columnBuilder.Append(character);
              }
  
              // If we are still inside a column add a new one
              if (inColumn)
                  Fields.Add(TrimColumns ? _columnBuilder.ToString().Trim() : _columnBuilder.ToString());   
          }
  
          /// <summary>
          /// Disposes of all unmanaged resources
          /// </summary>
          public void Dispose()
          {
              if (_streamReader != null)
              {
                  _streamReader.Close();
                  _streamReader.Dispose();
              }
  
              if (_streamWriter != null)
              {
                  _streamWriter.Close();
                  _streamWriter.Dispose();
              }
  
              if (_memoryStream != null)
              {
                  _memoryStream.Close();
                  _memoryStream.Dispose();
              }
  
              if (_fileStream != null)
              {
                  _fileStream.Close();
                  _fileStream.Dispose();
              }
  
              if ((_type == Type.String || _type == Type.File) && _stream != null)
              {
                  _stream.Close();
                  _stream.Dispose();
              }
          }
  
          #endregion Methods
  
      }

  
}